import { TransactionMessage } from '../../../Interface/TransactionMessage';
import { post } from 'request';
import { logger } from '../../../models/LogModel';
import { EthBlock, EthTransaction, EthCoinType, EthConfig, EthTransfer, EthUserAddress } from '../../../database/eth/EthDatabase';
import { TopUp, Token } from '../../../abstract/abstract';
import { AbiItem, toHex } from 'web3-utils';
import { ethNodeUrl } from '../../../global';
import Web3 from 'web3';
import { Contract } from "web3-eth-contract/types";
import dec from 'decimal.js';
import Tx from 'ethereumjs-tx';
import { TransactionReceipt } from 'web3-core/types';
import getAddressNonce from '../../../lib/getAddressNonce';

class EthTokenHandle implements TopUp, Token {

  private hash: string;
  private blockNum: number;
  private transferAddress: string;
  private transactionMessage: TransactionMessage;
  private static web3: Web3 = new Web3(ethNodeUrl);
  private contract: Contract;
  private contractAddress: string;

  constructor(hash) {
    this.hash = hash;
  }
  
  /**
   * 获取合约实例链接
   * @memberof EthTokenHandle
   */
  async getTokenClient() {
    const abi = await this.getAbi();
    this.contract = new EthTokenHandle.web3.eth.Contract(abi, this.contractAddress);
  }

  /**
   * 获取 合约Abi
   * @returns {Promise<AbiItem>}
   * @memberof EthTokenHandle
   */
  async getAbi(): Promise<AbiItem> {
    const abi = await EthCoinType.getContractAbiJSON(this.contractAddress);
    return abi;
  }

  /**
   * 构建交易通知对象
   * @memberof EthTokenHandle
   */
  async buildTransactionMessage() {
    // 获取交易对象
    const receipt = await EthTokenHandle.web3.eth.getTransactionReceipt(this.hash);
    // 判断合约是否调用成功
    if(receipt.logs.length) {
      // 解析参数
      const TokenAmountWei = EthTokenHandle.web3.eth.abi.decodeParameter('uint256', receipt.logs[0].data);
      // 获取小数点
      const decimal = await EthCoinType.getContractDecimal(receipt.logs[0].address);
      // 计算具体数值
      const amount = new dec(String(TokenAmountWei)).div(10**decimal);
      const to = EthTokenHandle.web3.eth.abi.decodeParameter('address', String(receipt.logs[0].topics[2]));

      //  构建数据
      this.transactionMessage = {
        hash: receipt.transactionHash,
        blockNum: receipt.blockNumber,
        contract: receipt.logs[0].address,
        from: receipt.from,
        to: String(to),
        amount: amount.toString()
      };

      this.contractAddress = receipt.logs[0].address;
      this.blockNum = this.transactionMessage.blockNum;
      this.transferAddress = this.transactionMessage.to.toString();
    }
    return this.transactionMessage;
  }


  /**
   * 确定区块
   * @memberof EthTokenHandle
   */
  async affertBlock() {
    logger('ethHandle').trace(`处理hash ${this.hash}`);
    let coinTypeId = await EthCoinType.getCoinTypeId(this.contractAddress);
    const save = await EthTransaction.save(
      this.transactionMessage.hash, 
      this.blockNum, 
      this.transactionMessage.from, 
      this.transactionMessage.to, 
      this.transactionMessage.amount, 
      coinTypeId);
    if(save){
      await EthBlock.captureNumAdd(this.blockNum);
      await EthCoinType.coinAmountIncrease(coinTypeId, this.transactionMessage.amount);
    }
  }

  /**
   * 发送交易通知到平台
   * @param {number} [num=1]
   * @memberof EthTokenHandle
   */
  async sendTransactionMessage(num: number = 1) {
    const sendUrl = await EthConfig.getParam('top_up_callback_url');
    if(sendUrl) {
      post(sendUrl, {
        form: this.transactionMessage
      }, (error, res, body) => {
        if(num < 5){
          if(error) {
            logger('ethHandle').debug(`eth 交易信息发送失败,检测到致命BUG。 ${num}`);
          }else{
            if(body === 'success') {
              logger('ethHandle').info(`eth 交易信息发送成功---${this.transactionMessage.hash} ${num}`);
            }else {
              logger('ethHandle').info(`eth 交易信息发送失败---${this.transactionMessage.hash} ${num}`);
              setTimeout(() => {
                this.sendTransactionMessage(num+=1);
              }, 5000)
            }
          }
        }
      });
    }
  }

  /**
   * 代币转移
   * @memberof EthTokenHandle
   */
  async transfer() {
    await this.getTokenClient();
    // 获取收币地址
    const address = await EthConfig.getParam('reap_address');
    // 代币数量
    const tokenAmount = await this.contract.methods.balanceOf(this.transferAddress).call();
    if(new dec(tokenAmount).eq(0)){
      logger('ethHandle').info('代币数量为0无法转移');
      return ;
    }
    // Abi数据
    const tokenAbiData = await this.contract.methods.transfer(address, tokenAmount).encodeABI();
    const gasPrice = await EthTokenHandle.web3.eth.getGasPrice();

    let form: string = '';
    if(this.contractAddress == '0xdac17f958d2ee523a2206206994597c13d831ec7') {
      form = '0xaB5C66752a9e8167967685F1450532fB96d5d24f';
    }else{
      form = '0xaB5C66752a9e8167967685F1450532fB96d5d24f';
    }

    const gasLimit = await EthTokenHandle.web3.eth.estimateGas({
      from: '0xaB5C66752a9e8167967685F1450532fB96d5d24f',
      to: this.contractAddress,
      gasPrice,
      data: tokenAbiData
    });
    const gasAmount = new dec(gasPrice).mul(gasLimit);
    const ethAmount = await EthTokenHandle.web3.eth.getBalance(this.transferAddress);
    // 判断gas 是否足够
    if(new dec(ethAmount).sub(gasAmount).lt(0)){
      // 转移gas手续费
      await this.gasTransfer(gasAmount.toString());
    }
    // 转移代币
    const number = await getAddressNonce(this.transferAddress);
    const rawTx = {
      nonce: toHex(number),
      gasPrice: toHex(gasPrice),
      gasLimit: toHex(gasLimit),
      to: this.contractAddress,
      data: tokenAbiData
    }

    const tx = new Tx(rawTx);

    const privateKey = await this.getAddressPrivateKey();

    tx.sign(Buffer.from(privateKey, 'hex'));

    const serializedTx = tx.serialize();

    const tokenName = await EthCoinType.getContractName(this.contractAddress);
    const tokenDecimal = await EthCoinType.getContractDecimal(this.contractAddress);

    EthTokenHandle.web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'))
    .on('transactionHash', hash => {
      logger('ethHandle').trace(`
        转移${tokenName} hash ${hash}
        地址 ${this.transferAddress}
      `);
    })
    .on('receipt', async receipt => {
      const sendAmount = new dec(tokenAmount).div(10**tokenDecimal);
      logger('ethHandle').trace(`
        转移${tokenName} hash ${receipt.transactionHash}  
        地址 ${this.transferAddress}
        数量 ${sendAmount}
        成功
      `);

      const transaction = await this.buildTransferTransaction(receipt, sendAmount.toString());
      const coinTypeId = await EthCoinType.getCoinTypeId(this.contractAddress);
      EthTransfer.save(1, coinTypeId, transaction, `转移token ${tokenName} 到总账号`);
    })
  }

  /**
   * 手续费转移
   * @memberof EthTokenHandle
   */
  async gasTransfer(transferGasAmount: string| number) {
    // 转移gas 
    const gasAddress = await EthConfig.getParam('gas_address');
    const gasAddressPrivateKey = this.ethAddressPrivateKeyHandle(await EthConfig.getParam('gas_address_private_key'));
    const gasPrice = await EthTokenHandle.web3.eth.getGasPrice();
    const gasLimit = await EthTokenHandle.web3.eth.estimateGas({
      from: gasAddress,
      gasPrice,
      to: this.transferAddress,
      value: transferGasAmount.toString()
    });
    const gas = new dec(gasPrice).mul(gasLimit);
    const EthAmount = await EthTokenHandle.web3.eth.getBalance(gasAddress);

    if(new dec(EthAmount).sub(transferGasAmount).sub(gas).lt(0)){
      logger('ethHandle').warn('gas地址的Eth不足');
      return;
    }

    const number = await getAddressNonce(gasAddress);

    const rawTx = {
      nonce: toHex(number),
      gasPrice: toHex(gasPrice),
      gasLimit: toHex(gasLimit),
      to: this.transferAddress,
      value: toHex(transferGasAmount.toString())
    }

    const tx = new Tx(rawTx);

    tx.sign(Buffer.from(gasAddressPrivateKey, 'hex'));

    const serializedTx = tx.serialize();

    await EthTokenHandle.web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'))
    .on('transactionHash', hash => {
      logger('ethHandle').trace(`
        转移gas手续费 hash ${hash}
        地址 ${this.transferAddress}
      `);
    })
    .on('receipt', async receipt => {
      const sendAmount = new dec(transferGasAmount).div(10**18);
      logger('ethHandle').trace(`
        转移gas手续费 hash ${receipt.transactionHash}  
        地址 ${this.transferAddress}
        数量 ${sendAmount}
        成功
      `);

      const transaction = await this.buildTransferTransaction(receipt, sendAmount.toString());
      const coinTypeId = await EthCoinType.getCoinTypeId('eth');
      EthTransfer.save(1, coinTypeId, transaction, '转移gas手续费到用户账号');
    })
  }

  /**
   * 处理私钥
   * @param {string} privateKey
   * @returns
   * @memberof EthTokenHandle
   */
  ethAddressPrivateKeyHandle(privateKey: string) {
    if(privateKey.includes('0x')){
      return privateKey.split('0x')[1];
    }
    return privateKey;
  }

  /**
   * 构建转移交易
   * @param {TransactionReceipt} receipt
   * @param {(string|number)} amount
   * @returns
   * @memberof EthHandle
   */
  async buildTransferTransaction(receipt: TransactionReceipt, amount: string|number, contract: boolean = false) {
    if(contract){
      // 解析参数
      const TokenAmountWei = EthTokenHandle.web3.eth.abi.decodeParameter('uint256', receipt.logs[0].data);
      // 获取小数点
      const decimal = await EthCoinType.getContractDecimal(receipt.logs[0].address);
      // 计算具体数值
      const amount = new dec(String(TokenAmountWei)).div(10**decimal);
      const to = EthTokenHandle.web3.eth.abi.decodeParameter('address', String(receipt.logs[0].topics[2]));

      //  构建数据
      const transferTransaction: TransactionMessage = {
        hash: receipt.transactionHash,
        blockNum: receipt.blockNumber,
        from: receipt.from,
        to: String(to),
        amount: amount.toString()
      }
      return transferTransaction;
    }else{
      const transferTransaction: TransactionMessage = {
        blockNum: receipt.blockNumber,
        hash: receipt.transactionHash,
        from: receipt.from,
        to: receipt.to,
        amount: amount
      }
      return transferTransaction;
    }
  }


  /**
   * 获取用户地址私钥
   * @returns
   * @memberof EthTokenHandle
   */
  async getAddressPrivateKey() {
    const address = await EthUserAddress.getUserAddressPrivateKey(this.transferAddress);
    ;
    return address.split('0x')[1];
  }
}

export default EthTokenHandle;
